/** 
 * Copyright 2010 Erlacher Felix, Estgfaeller Wolfgang, Ferula Patrick
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package at.socialconference.web.server;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import twitter4j.Status;

import at.socialconference.inter.connection.SocketConnector;
import at.socialconference.inter.connection.protocol.SocketProtocol;
import at.socialconference.inter.constants.Countries;
import at.socialconference.inter.model.Conference;
import at.socialconference.inter.model.Login;
import at.socialconference.inter.model.Place;
import at.socialconference.inter.model.Talk;
import at.socialconference.inter.model.Updatetable;
import at.socialconference.inter.model.User;
import at.socialconference.web.client.SCwebService;
import at.socialconference.web.server.util.DBLPHelper;
import at.socialconference.web.server.util.TwitterHelper;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * Implements the methods provided by the GWT webservice
 */

public class SCwebServiceImpl extends RemoteServiceServlet implements SCwebService, HttpSessionListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = -1627943820211814545L;

	private Map<String, ClientSessionInformation> sessionInformation = new HashMap<String, ClientSessionInformation>();
	
	//is needed global because in getTimelines we need it, 
	//and in getConfs we get it, so we dont have to get it twice, nice!
	private List<Conference> confs=null;
	
	private class ClientSessionInformation {
		public String sessionID;
		public User user;
		public SocketConnector connector;
		public TwitterHelper twitter;
		

		public ClientSessionInformation(String id) {
			this.sessionID = id;
		}
	}

	public Boolean existsValidSession() {
		ClientSessionInformation csi = getClientSessionInformation();
		if (csi!=null && csi.connector!=null && csi.twitter != null && csi.user != null) {
			System.out.println("active valid session id: " + csi.sessionID + " connected");
			//only true if a valid ClientSessionInformation object exists
			return true;
		} else {
			return false;
		}
	}
	
	public Integer login(String user, String pwd) {
		if (existsValidSession()==true) {
			// no need to create full session information since the previous client
			// session is still valid and the mapping contains session information
			ClientSessionInformation csi = getClientSessionInformation();
			try {
				csi.user = csi.connector.getUser();
			} catch (Exception e) {
				System.out.println("Could not update User object from DB");
				e.printStackTrace();
			}
			return SocketProtocol.OK;
		}
		SocketConnector userConnector = new SocketConnector();
		int status = userConnector.checkLogin(user, pwd);
		if (status == SocketProtocol.OK) {
			if (mapSessionInformation(userConnector) == false) {
				return SocketProtocol.RESP_SESSION_CREATION_FAILED;
			}
		}
		return status;
	}

	public void logout() {
		HttpSession hs = getThreadLocalRequest().getSession(false);
		if (hs != null) {
			hs.invalidate();
		}
	}

	public TreeMap<Integer, String> getCountries() {
		return Countries.getCountries();
	}

	public TreeMap<String, String> getDBLPLinks(String firstName, String lastName) {
		return DBLPHelper.fetchByName(firstName, lastName);
	}

	public User getClientUser() {
		ClientSessionInformation csi = getClientSessionInformation();
		//TODO quick n dirty
		try {
			csi.user = csi.connector.getUser();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return csi.user;
	}

	public Integer register(User user, Login login) {
		SocketConnector tempConnector = new SocketConnector();
		int status = -1;
		try {
			status = tempConnector.register(user, login);
		} catch (Exception e) {
			System.out.println("unable to register user");
			e.printStackTrace();
		}
		return status;
	}

	public String getAuthURL() {
		// get the ClientSessionInformation object
		ClientSessionInformation csi = getClientSessionInformation();
		return csi.twitter.getAuthURL();
	}

	public Integer createAccToken(String pin, boolean store) {
		// get the ClientSessionInformation object
		ClientSessionInformation csi = getClientSessionInformation();
		int status = csi.twitter.createAccToken(pin);
		if (status > 0) {
			if (store) {
				//TODO: er erstellt perfekt das token und stored es auch, aber es wird
				//nix returned, stored man nicht bekommt l�uft alles glatt
				storeToken();
			}
		}
		return status;
	}

	private void storeToken() {
		ClientSessionInformation csi = getClientSessionInformation();
		User clientUser = getClientUser();
		clientUser.setTw_access_token(csi.twitter.getAccessToken().getToken());
		clientUser.setTw_access_secret(csi.twitter.getAccessToken()
				.getTokenSecret());
		try {
			csi.connector.updateObject(clientUser);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public Integer follow(int id) {
		// get the ClientSessionInformation object
		ClientSessionInformation csi = getClientSessionInformation();
		return csi.twitter.follow(id);
	}
	
	public Integer unFollow(int id) {
		// get the ClientSessionInformation object
		ClientSessionInformation csi = getClientSessionInformation();
		return csi.twitter.unFollow(id);
	}

	public String getTimelines() {
		// get the ClientSessionInformation object
		String result="";
		ClientSessionInformation csi = getClientSessionInformation();
		List<Status> statuses=csi.twitter.getTimelines();
		
		//should never happen because at this point in all use cases, the 
		//user already got presented the conferences and thus getConferences()
		//has already been called. Anyway, if someone screwed with it gets checked here
		if (confs==null){
			//get the list of conferences
			try {
				confs= csi.connector.getConferences();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		//this happens if a 401 error is returned, very likely the user rovoked access to the app
		if (confs==null){
			return "401";
		}
		//go through list and put it in a string
		for (Status status : statuses) {
			//only show timelines of our conferences!
			for(int i=0;i<confs.size();i++){
				//checking ids, so confs can change name and are still displayed!
				if(confs.get(i).getId()==status.getUser().getId())
					result = result + status.getUser().getName() + ": " + status.getText() + "\n";
			}
		}
		
		return result; 
	}

	public List<User> searchUser(String firstName, String lastName, int countryCode) {
		// get the ClientSessionInformation object
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.searchUser(firstName, lastName, countryCode);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public List<Conference> getConferences() {
		// get the ClientSessionInformation object
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			// set confs and return the conferences if the client connector != null
			confs= csi.connector.getConferences();
			return confs;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public String getFriends(){
		String result="";
		ClientSessionInformation csi = getClientSessionInformation();
		int[] ids= csi.twitter.getFriends();
		if(ids==null){
			return "Error:401";
		}
		
		//should never happen because at this point in all use cases, the 
		//user already got presented the conferences and thus getConferences()
		//has already been called. Anyway, if someone screwed with it gets checked here
		if (confs==null){
			//get the list of conferences
			try {
				confs= csi.connector.getConferences();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		for(int i=0;i<ids.length;i++){
			int temp=ids[i];
			for(int j=0;j<confs.size();j++){
				if(confs.get(j).getId()==temp){
					result=result+confs.get(j).getName()+"\n";
				}
			}
		}
		
//		for(Integer i:ids){
//			for(int j=0;j<confs.size();j++){
//				if(confs.get(j).getId()==i.intValue()){
//					result=result+confs.get(j).getName()+"\n";
//				}
//			}
//		}
	
		
		return result;
		
	}
	
	@Override
	public Integer friendRequest(int id) {
		ClientSessionInformation csi = getClientSessionInformation();
		int status = -1;
		try {
			status = csi.connector.checkFriendStatus(id);
		} catch (IOException e) {
			System.out.println("Couldn't check friendship status for id: " + id);
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("Couldn't check friendship status for id: " + id);
			e.printStackTrace();
		}
		if (status==SocketProtocol.RESP_FSTATUS_NOFRIENDSHIP) {
			try {
				status = csi.connector.friendRequest(id);
			} catch (Exception e) {
				System.out.println("Couldn't request friendship with id: " + id);
				e.printStackTrace();
			}
		}
		return status;
	}
	
	@Override
	public List<User> getFriendRequests() {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.getFriendRequests();
		} catch (Exception e) {
			System.out.println("Error: could not retrieve friend requests");
			e.printStackTrace();
		}
		return null;
	}

	private ClientSessionInformation getClientSessionInformation() {
		HttpSession hs = getThreadLocalRequest().getSession(false);
		if (hs != null && sessionInformation.containsKey(hs.getId())) {
			// return the mapped ClientSessionInformation object if it exists
			return sessionInformation.get(hs.getId());
		} else {
			// invalidate the session if it exists and return null
			if (hs != null) {
				hs.invalidate();
			}
			return null;
		}
	}

	/**
	 * This method binds client information to a session. It creates a ClientSessionInformation
	 * object and fills it with client related information. The Map 'sessionInformation' maps it
	 * to the current session id.
	 * @param connector Has to be a valid connector which is in status logged in
	 */
	private boolean mapSessionInformation(SocketConnector connector) {
		// get the current session if it exists, but do not create a new one
		HttpSession hs = getThreadLocalRequest().getSession(false);

		if (hs == null) {
			// create a new session if there was no valid one
			hs = getThreadLocalRequest().getSession(true);
		}

		// get the session id
		String id = hs.getId();

		ClientSessionInformation csi = new ClientSessionInformation(id);

		// add SocketConnector, TwitterHelper and User to the
		// ClientSessionInformation object
		csi.connector = connector;
		csi.twitter = new TwitterHelper();
		try {
			csi.user = csi.connector.getUser();
		} catch (Exception e) {
			System.out.println("Could not retrieve User object from DB");
			hs.invalidate();
			e.printStackTrace();
			return false;
		}

		// if the user
		if (csi.user.hasSecrets()) {
			csi.twitter.createAccToken(csi.user.getTw_access_token(), csi.user.getTw_access_secret());
		}

		synchronized (this) {
			// map the session to the ClientSessionInformation object
			sessionInformation.put(id, csi);
		}

		return true;
	}

	/**
	 * HttpSessionListener Method
	 */
	@Override
	public void sessionCreated(HttpSessionEvent arg0) {
		System.out.println("opened session with id:" + arg0.getSession().getId());
	}
	

	/**
	 * HttpSessionListener Method
	 */
	@Override
	public void sessionDestroyed(HttpSessionEvent arg0) {
		String id = arg0.getSession().getId();
		if (sessionInformation.containsKey(id)) {
			ClientSessionInformation csi = sessionInformation.get(id);
			if (csi.connector != null) {
				try {
					// close the socket connection
					csi.connector.close();
				} catch (Exception e) {
					e.printStackTrace();
					System.out.println("Failed to close client SocketConnector");
				}
			}
			synchronized (this) {
				// remove session information from mapping
				sessionInformation.remove(id);
			}
		}
		System.out.println("closed session with id:" + id);
	}

	@Override
	public Integer acceptFriendRequest(int id) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.acceptFriendRequest(id);
		} catch (Exception e) {
			System.out.println("Could not accept friendship with" + id);
			e.printStackTrace();
		}
		return -1;
	}

	@Override
	public Integer rejectFriendRequest(int id) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.rejectFriendRequest(id);
		} catch (Exception e) {
			System.out.println("Could not reject friendship with" + id);
			e.printStackTrace();
		}
		return -1;
	}

	@Override
	public Integer updateObject(Updatetable object) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.updateObject(object);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return -1;
	}

	@Override
	public List<Place> getPlaces() {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.getPlaces();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Integer deleteObject(Updatetable object) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.deleteObject(object);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return -1;
	}

	@Override
	public Integer insertObject(Updatetable object) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.insertObject(object);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return -1;
	}

	@Override
	public List<Talk> getTalks() {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.getTalks();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Boolean isEnrolled(Integer id) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.isEnrolled(id);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public Integer enroll(Integer id) {
		ClientSessionInformation csi = getClientSessionInformation();
		try {
			return csi.connector.enroll(id);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return -1;
	}
}
